#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <nginx.h>

#ifndef NGX_HTTP_MAX_CAPTURES
#define NGX_HTTP_MAX_CAPTURES 9
#endif

typedef struct{	
	ngx_flag_t    waf_filter_flag;     // on or off 
	ngx_str_t     waf_filter_pattern;    // destination pattern
    ngx_regex_t  *match_regex;
}ngx_http_waf_filter_main_conf_t;

// context function 
static ngx_int_t   ngx_http_waf_filter_postconfig(ngx_conf_t *cf);
static void*       ngx_http_waf_filter_create_conf(ngx_conf_t *cf);
static char*       ngx_http_waf_filter_merge_conf(ngx_conf_t *cf, void *prev, void *conf);

// commands function
static char*
ngx_http_cmdset_waf_filter_pattern(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

// save filter:  ngx_http_top_header_filter  / ngx_http_top_body_filter 
static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;  
static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;

// dofilter
static ngx_int_t ngx_http_waf_filter_header_filter(ngx_http_request_t *r);
static ngx_int_t ngx_http_waf_filter_body_filter(ngx_http_request_t *r, ngx_chain_t *chain);

/***************** frame+  *************************/
static ngx_command_t ngx_http_waf_filter_commands[] = {
	{
		ngx_string("waf_filter_flag"),       // the commmand name
		NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE1 | NGX_CONF_FLAG ,
		ngx_conf_set_flag_slot,			// the command handler
		NGX_HTTP_MAIN_CONF_OFFSET,
        0,
		NULL
	},
	{
		ngx_string("waf_filter_pattern"),
		NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE1,
   	    ngx_http_cmdset_waf_filter_pattern,	
		NGX_HTTP_MAIN_CONF_OFFSET,
		offsetof(ngx_http_waf_filter_main_conf_t, waf_filter_pattern),
		NULL
	},
	ngx_null_command
};
 
static ngx_http_module_t ngx_http_waf_filter_module_ctx ={
	NULL,									/* preconfiguraton */
	ngx_http_waf_filter_postconfig,         /* postconfiguration */
	ngx_http_waf_filter_create_conf,		/* create main configuration */
	NULL,									/* init main configuration */
	NULL,									/* create server configuration */
	NULL,									/* mersge server configuration */
    NULL,
    NULL
//	ngx_http_waf_filter_create_conf,		/* create location configuration */
//	ngx_http_waf_filter_merge_conf          /* merge location configuration */
};

ngx_module_t ngx_http_waf_filter_module = {
	NGX_MODULE_V1,
	&ngx_http_waf_filter_module_ctx,
	ngx_http_waf_filter_commands,
	NGX_HTTP_MODULE,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NGX_MODULE_V1_PADDING
};
/***************** frame-  *************************/

//context configuration
static ngx_int_t   ngx_http_waf_filter_postconfig(ngx_conf_t *cf){
    ngx_http_next_header_filter = ngx_http_top_header_filter;
    ngx_http_top_header_filter = ngx_http_waf_filter_header_filter;
 
    ngx_http_next_body_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_waf_filter_body_filter;
    return NGX_OK;		
}

__attribute__((unused))
static void* ngx_http_waf_filter_create_conf(ngx_conf_t *cf){
    ngx_http_waf_filter_main_conf_t *waf_filter_mcf;
    waf_filter_mcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_waf_filter_main_conf_t));
    if (waf_filter_mcf == NULL){
        return NGX_CONF_ERROR;
    }
    /*
     * todo: check init 
     * set by ngx_pcalloc():
     * waf_filter_mcf->waf_filter_pattern = { 0, NULL };
     * ngx_regex_t  *match_regex = NULL;
     * ngx_int_t     matedTimes = 0;
     */
    waf_filter_mcf->waf_filter_flag = NGX_CONF_UNSET;
        
    return waf_filter_mcf;
}

__attribute__((unused))
static char* ngx_http_waf_filter_merge_conf(ngx_conf_t *cf, void *prev_param, void *conf_param){
    ngx_http_waf_filter_main_conf_t *prev = prev_param;
    ngx_http_waf_filter_main_conf_t *conf = conf_param;
    
    ngx_conf_merge_value(conf->waf_filter_flag, prev->waf_filter_flag, 0);
    ngx_conf_merge_str_value(conf->waf_filter_pattern, prev->waf_filter_pattern, "");
    
    return NGX_CONF_OK; 
}

//ngx_command_t 
static char*
ngx_http_cmdset_waf_filter_pattern(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){
    ngx_http_waf_filter_main_conf_t  * wfmcf = conf;
// 1. compile
// 1.1 get source string
    ngx_int_t n;
    n = cf->args->nelts;
    if (n > 2){
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "only one regex is allowed");
        return NGX_CONF_ERROR;
    }
    
    // ngx_conf_set_str_slot
    ngx_str_t *value;
    value = cf->args->elts;
    wfmcf->waf_filter_pattern = value[1];
    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "[waf_module] 1.1 get source string!wfmcf->waf_filter_pattern=%V", &wfmcf->waf_filter_pattern);
    
// 1.2 compile source string
#if (NGX_PCRE)
    u_char  errstr[NGX_MAX_CONF_ERRSTR];
    ngx_str_t  err;
    err.len = NGX_MAX_CONF_ERRSTR;
    err.data = errstr;

    ngx_regex_compile_t  rc;
    ngx_memset(&rc, 0, sizeof(ngx_regex_compile_t));
    rc.pattern = wfmcf->waf_filter_pattern;
    rc.pool    = cf->pool; 
    rc.err     = err;
    rc.options = 0;     // NGX_REGEX_CASELESS

    if ( ngx_regex_compile(&rc) != NGX_OK) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err);
        return NGX_CONF_ERROR;
    }

     ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "[waf_module] ngx_regex_compile ok!");
// 2. save to ngx_http_waf_filter_main_conf_t
    wfmcf->match_regex = rc.regex; 
    if(wfmcf->match_regex == NULL){
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "[waf_module] %V", &err);
        return NGX_CONF_ERROR;
    }
#else
    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
            "the use of the regex \"%V\" requires PCRE library", wfmcf->waf_filter_pattern);
    return NGX_CONF_ERROR;
#endif
    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "[waf_module] conf init ok!");
    return NGX_CONF_OK;
}

/***************** dofilter+  *************************/
static ngx_int_t ngx_http_waf_filter_header_filter(ngx_http_request_t *r){

    ngx_log_t *log;
    ngx_str_t dest ;
    ngx_str_null(&dest);

    log = r->connection->log;

    ngx_log_debug(NGX_LOG_DEBUG_HTTP, log, 0, "[waf_filter] ngx_http_waf_filter_header_filter in");

    dest.len = r->request_end - r->uri_start;
    dest.data = r->uri_start;

    ngx_http_waf_filter_main_conf_t *wfmcf;
    
    // check flag
    wfmcf = ngx_http_get_module_main_conf(r, ngx_http_waf_filter_module);
    if (wfmcf == NULL){
        ngx_log_debug(NGX_LOG_DEBUG_HTTP, log, 0, "[waf_filter] ngx_http_get_module_main_conf failed!");
        return NGX_ERROR;
    }
    if (!wfmcf->waf_filter_flag ){
        ngx_log_debug(NGX_LOG_DEBUG_HTTP, log, 0, "[waf_filter] waf_filter_flag is off!");
        return ngx_http_next_header_filter(r);
    }

    // check param
    ngx_log_debug(NGX_LOG_DEBUG_HTTP, log, 0, "[waf_filter] check param +");
    if (dest.data == NULL || dest.len == 0){
        return ngx_http_next_header_filter(r);
    }
    ngx_log_debug(NGX_LOG_DEBUG_HTTP, log, 0, "[waf_filter] check param -");
    
    // todo: regex match
    ngx_log_debug(NGX_LOG_DEBUG_HTTP, log, 0, "[waf_filter] regex match +");
    
    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "[waf_filter] pattern=%V" , &dest);

    ngx_int_t  matchedTimes;
    matchedTimes = (NGX_HTTP_MAX_CAPTURES + 1) *3 ;
    int *matched_vector = ngx_pcalloc(r->pool, matchedTimes * sizeof(int) );
    if (matched_vector == NULL){
        ngx_log_error(NGX_LOG_EMERG, log, 0, "ngx_pcalloc failed!" );
        return ngx_http_next_header_filter(r);
    }
    
    ngx_int_t rc = ngx_regex_exec(wfmcf->match_regex, &dest, matched_vector , matchedTimes );
    if(rc != NGX_REGEX_NO_MATCHED){
        ngx_log_debug(NGX_LOG_DEBUG_HTTP, log, 0, "[waf_filter]: hit!" );
        return NGX_HTTP_CLIENT_CLOSED_REQUEST ;
    }
    ngx_log_error(NGX_LOG_EMERG, log, 0, "1-");
    // recover the origin string
    return ngx_http_next_header_filter(r);
}

static ngx_int_t ngx_http_waf_filter_body_filter(ngx_http_request_t *r, ngx_chain_t *chain){
    // 1.0.0 version does not filter content
    return ngx_http_next_body_filter(r, chain);

}
